#############################################################################

namespace eval ::logger {
    custom::defgroup Logging [::msgcat::mc "Logging options."] -group Chat

    custom::defvar options(logdir) [file join $::configdir logs] \
	[::msgcat::mc "Directory to store logs."] \
	-type string -group Logging

    custom::defvar options(log_chat) 1 \
	[::msgcat::mc "Store private chats logs."] \
	-type boolean -group Logging

    custom::defvar options(log_groupchat) 1 \
	[::msgcat::mc "Store group chats logs."] \
	-type boolean -group Logging

    custom::defvar options(log_normal) 1 \
	[::msgcat::mc "Store messages."] \
	-type boolean -group Logging


    variable version 1.0

    if {![file exists $options(logdir)]} {
	file mkdir $options(logdir)
    
	# Storing version for possible future conversions
	set fd [open [file join $options(logdir) version] w]
	puts $fd $version
	close $fd
    }

    array set m2d [list [::msgcat::mc "January"]   01 \
			[::msgcat::mc "February"]  02 \
			[::msgcat::mc "March"]     03 \
			[::msgcat::mc "April"]     04 \
			[::msgcat::mc "May"]       05 \
			[::msgcat::mc "June"]      06 \
			[::msgcat::mc "July"]      07 \
			[::msgcat::mc "August"]    08 \
			[::msgcat::mc "September"] 09 \
			[::msgcat::mc "October"]   10 \
			[::msgcat::mc "November"]  11 \
			[::msgcat::mc "December"]  12]

    array set d2m [list 01 [::msgcat::mc "January"]   \
			02 [::msgcat::mc "February"]  \
			03 [::msgcat::mc "March"]     \
			04 [::msgcat::mc "April"]     \
			05 [::msgcat::mc "May"]       \
			06 [::msgcat::mc "June"]      \
			07 [::msgcat::mc "July"]      \
			08 [::msgcat::mc "August"]    \
			09 [::msgcat::mc "September"] \
			10 [::msgcat::mc "October"]   \
			11 [::msgcat::mc "November"]  \
			12 [::msgcat::mc "December"]]
}

#############################################################################

proc ::logger::add_menu_item {state category m connid jid} {
    switch -- $category {
	roster {
	    set rjid [roster::find_jid $connid $jid]
	    if {$rjid != ""} {
		set jid $rjid
	    }
	}
	chat {
	    set nas [node_and_server_from_jid $jid]
	    if {![chat::is_groupchat [chat::chatid $connid $nas]]} {
		set jid $nas
	    }
	}
    }
    $m add command -label [::msgcat::mc "Show history"] \
	   -state $state \
           -command [list logger::show_log $jid -connection $connid]
}

#############################################################################

hook::add chat_create_user_menu_hook \
    [list ::logger::add_menu_item normal chat] 65
hook::add chat_create_conference_menu_hook \
    [list ::logger::add_menu_item normal group] 65
hook::add roster_create_groupchat_user_menu_hook \
    [list ::logger::add_menu_item normal grouproster] 65
hook::add roster_conference_popup_menu_hook \
    [list ::logger::add_menu_item normal roster] 65
hook::add roster_service_popup_menu_hook \
    [list ::logger::add_menu_item disabled roster] 65
hook::add roster_jid_popup_menu_hook \
    [list ::logger::add_menu_item normal roster] 65
hook::add message_dialog_menu_hook \
    [list ::logger::add_menu_item disabled message] 65
hook::add search_popup_menu_hook \
    [list ::logger::add_menu_item disabled search] 65

#############################################################################

proc ::logger::str_to_log {str} {
    return [string map {\\ \\\\ \r \\r \n \\n} $str]
}

#############################################################################

proc ::logger::log_to_str {str} {
    return [string map {\\\\ \\ \\r \r \\n \n} $str]
}

#############################################################################

proc ::logger::jid_to_filename {jid} {
    set utf8_jid [encoding convertto utf-8 $jid]
    set len [string length $utf8_jid]
    set filename ""
    for {set i 0} {$i < $len} {incr i} {
	binary scan $utf8_jid @${i}c sym
	set sym [expr {$sym & 0xFF}]
	switch -- $sym {
	    34 - 37 - 39 - 42 - 43 - 47 - 58 - 59 - 60 - 62 - 63 - 92 - 124 - 126 {
		# 34 " 37 % 39 ' 42 * 43 + 47 / 58 : 59 ; 60 < 62 > 63 ? 92 \ 124 | 126 ~
		append filename [format "%%%02X" $sym]
	    }
	    46 {
		# 46 .
		if {$i + 1 == $len} {
		    append filename [format "%%%02X" $sym]
		} else {
		    append filename [binary format c $sym]
		}
	    }
	    default {
		if {$sym >= 128 || $sym <= 32} {
		    append filename [format "%%%02X" $sym]
		} else {
		    append filename [binary format c $sym]
		}
	    }
	}
    }
    if {[string index $filename 253] == "%"} {
	return [string range $filename 0 252]
    }
    if {[string index $filename 252] == "%"} {
	return [string range $filename 0 251]
    }
    return [string range $filename 0 253]
}

#############################################################################

proc ::logger::filename_to_jid {filename} {
    set len [string length $filename]
    set utf8_jid ""
    for {set i 0} {$i < $len} {incr i} {
	catch {
	    binary scan $filename @${i}a sym
	    switch -- $sym {
		"%" {
		    incr i
		    binary scan $filename @${i}a2 num
		    append utf8_jid [binary format c 0x$num]
		    incr i
		}
		default {
		    append utf8_jid $sym
		}
	    }
	}
    }
    return [encoding convertfrom utf-8 $utf8_jid]
}

#############################################################################

proc ::logger::cdopen {filepath {mode r}} {
    set dir [file dirname $filepath]
    set file [file tail $filepath]
    set current_dir [pwd]
    cd $dir
    if {[catch {open $file $mode} fd]} {
	cd $current_dir
	return -code error $fd
    } else {
	cd $current_dir
	return $fd
    }
}

#############################################################################

proc ::logger::log_message {chatid from type body x} {
    variable options

    if {$type == "chat" && !$options(log_chat)} return
    if {$type == "groupchat" && !$options(log_groupchat)} return
    if {$type == "normal" && !$options(log_normal)} return

    set connid [chat::get_connid $chatid]
    set jid [chat::get_jid $chatid]
    set nas [node_and_server_from_jid $jid]
    if {$type == "chat" && ![chat::is_groupchat [chat::chatid $connid $nas]]} {
	set jid $nas
    }

    set nick [chat::get_nick $connid $from $type]

    set seconds [jlib::x_delay $x]
    foreach xelem $x {
	jlib::wrapper:splitxml $xelem tag vars isempty chdata children
	
	# Don't log message if this 'empty' tag is present. It indicates
	# messages history in chat window.
	if {[cequal $tag ""] && \
		[cequal [jlib::wrapper:getattr $vars xmlns] tkabber:x:nolog]} {
	    return
	}
    }
    set ts [clock format $seconds -format "%Y%m%dT%H%M%S" -gmt 1]
    set year [clock format $seconds -format %Y]
    set month [clock format $seconds -format %m]

    file mkdir [file join $options(logdir) $year $month]
    set fd [cdopen [file join $options(logdir) $year $month [jid_to_filename $jid]] a]
    fconfigure $fd -encoding utf-8
    puts $fd [str_to_log [list timestamp $ts jid $from nick $nick body $body]]
    close $fd
}

hook::add draw_message_hook ::logger::log_message 15

#############################################################################

proc ::logger::winid {name} {
    set allowed_name [jid_to_tag $name]
    return .log_$allowed_name
}

#############################################################################

proc ::logger::describe_month {year-month} {
    variable d2m

    lassign [split ${year-month} -] year month
    return "$d2m($month) $year"
}

#############################################################################

proc ::logger::create_log_viewer {lw jid args} {
    global font
    global tcl_platform
    global defaultnick

    foreach {key val} $args {
	switch -- $key {
	    -connection { set connid $val }
	    -subdirs { set subdirs $val }
	}
    }
    if {![info exists connid]} {
	set connid [lindex [jlib::connections] 0]
    }

    set logfile [jid_to_filename $jid]

    set mynick [get_group_nick $jid ""]

    toplevel $lw -relief $::tk_relief -borderwidth $::tk_borderwidth -class Chat
    wm group $lw .
    wm withdraw $lw
    set title [format [::msgcat::mc "History for %s"] $jid]
    wm title $lw $title
    wm iconname $lw $title

    set lf [ScrolledWindow $lw.sw]
    set l [text $lw.log -font $font -wrap word -takefocus 0]

    set mf [frame $lw.mf]
    pack $mf -side top -fill x -expand no -padx 1m -pady 1m
    set mlabel [label $mf.mlabel -text [::msgcat::mc "Select month:"]]
    pack $mlabel -side left
    set ebutton [button $mf.ebutton -text [::msgcat::mc "Export to XHTML"] \
				    -command [list [namespace current]::export \
						   $l $lw.mf.mcombo $logfile $mynick]]
    pack $ebutton -side right
    pack $lf -padx 1m -pady 1m -fill both -expand yes

    $lf setwidget $l

    regsub -all %W [bind Text <Prior>] $l prior_binding
    regsub -all %W [bind Text <Next>] $l next_binding
    bind $lw <Prior> $prior_binding
    bind $lw <Next> $next_binding

    $l tag configure they -foreground [option get $lw theyforeground Chat]
    $l tag configure me -foreground [option get $lw meforeground Chat]
    $l tag configure server_lab \
       -foreground [option get $lw serverlabelforeground Chat]
    $l tag configure server \
       -foreground [option get $lw serverforeground Chat]

    $l configure -state disabled

    if {![info exists subdirs]} {
	set subdirs [get_subdirs $logfile]
    }

    set ympairs {}
    foreach sd [lsort -decreasing $subdirs] {
	lappend ympairs [describe_month $sd]
    }
    lappend ympairs [::msgcat::mc "All"]

    set mcombo [ComboBox $mf.mcombo \
			 -editable no \
			 -exportselection no \
			 -values $ympairs \
			 -text [lindex $ympairs 0] \
			 -modifycmd [list \
			    [namespace current]::change_month \
					  $mf.mcombo $logfile $l $mynick]]
    pack $mcombo -side left

    hook::run open_log_post_hook $connid $jid $lw

    wm deiconify $lw
}

#############################################################################

proc ::logger::show_log {jid args} {
    set lw [winid $jid]
    debugmsg plugins "LOGGER: $lw"

    variable $lw
    upvar 1 $lw state

    if {![winfo exists $lw]} {
	eval [list create_log_viewer $lw $jid] $args
    } else {
	focus -force $lw
    }

    foreach {key val} $args {
	switch -- $key {
	    -when { set when $val }
	    -timestamp { set timestamp $val }
	}
    }

    set logfile [jid_to_filename $jid]
    set mynick [get_group_nick $jid ""]

    set log $lw.log
    set cbox $lw.mf.mcombo

    set ympairs [$cbox cget -values]
    if {[info exists when]} {
	set text [describe_month $when]
	if {[lsearch -exact $ympairs $text] < 0} {
	    error "no log entries for: $when"
	}
    } else {
	set text [lindex $ympairs 0]
    }

    $cbox configure -text $text

    change_month $cbox $logfile $log $mynick

    if {[info exists timestamp]} {
	set pos [lindex [$log tag ranges TS-$timestamp] 0]
	if {$pos == ""} { set pos end }
    } else {
	set pos end
    }

    $log see $pos
}

#############################################################################

proc ::logger::get_subdirs {logfile} {
    variable options

    set subdirs {}
    foreach yeard [glob -nocomplain -type d -directory $options(logdir) *] {
	foreach monthd [glob -nocomplain -type d -directory $yeard *] {
	    if {[file exists [file join $monthd $logfile]]} {
		lappend subdirs [file tail $yeard]-[file tail $monthd]
	    }
	}
    }

    return $subdirs
}

#############################################################################

proc ::logger::draw_messages {l hist mynick} {
    $l configure -state normal
    $l delete 1.0 end

    add_messages $l $hist $mynick
}

#############################################################################

proc ::logger::formatxmppts {xmppts} {
    set seconds [clock scan $xmppts -gmt 1]
    clock format $seconds -format {%Y-%m-%d %X}
}

#############################################################################

proc ::logger::exists_and_empty {what} {
    upvar 1 $what var
    expr {[info exists var] && $var == ""}
}

proc ::logger::exists_and_nonempty {what} {
    upvar 1 $what var
    expr {[info exists var] && $var != ""}
}

proc ::logger::add_messages {l hist mynick} {
    $l configure -state normal

    foreach vars $hist {
	array unset tmp
	if {[catch {array set tmp $vars}]} continue

	if {[info exists tmp(timestamp)]} {
	    $l insert end \[[formatxmppts $tmp(timestamp)]\] \
		[list TS-$tmp(timestamp)]
	}
	if {[exists_and_empty tmp(jid)]} {
	    # synthesized message
	    $l insert end "---" server_lab
	    set servertag server
	} else {
	    if {[exists_and_empty tmp(nick)]} {
		# message from the server:
		$l insert end "---" server_lab
		set servertag server
	    } else {
		set nick $tmp(nick)
		if {[string equal $nick $mynick]} {
		    set tag me
		} else {
		    set tag they
		}
		if {[info exists tmp(body)] && [regsub {^/me } $tmp(body) {} body]} {
		    $l insert end "*$nick $body" $tag
		    unset tmp(body)
		} else {
		    $l insert end "<$nick>" $tag
		}
		set servertag ""
	    }
	}
	if {[info exists tmp(body)]} {
	    $l insert end " $tmp(body)" $servertag
	}
	if {![$l compare "end -1 chars linestart" == "end -1 chars"]} {
	    $l insert end "\n"
	}
    }
    $l configure -state disabled
}

#############################################################################

proc ::logger::change_month {mcombo logfile l mynick} {
    variable m2d

    set month [$mcombo cget -text]
    if {$month == [::msgcat::mc "All"]} {
	draw_messages $l {} $mynick
	foreach m [lsort -increasing [get_subdirs $logfile]] {
	    add_messages $l [read_hist_from_file $logfile $m] $mynick
	    update
	}
    } else {
	set my_list [split $month " "]
	set month [lindex $my_list end]-$m2d([join [lrange $my_list 0 end-1] " "])
	draw_messages $l [read_hist_from_file $logfile $month] $mynick
    }
    $l see end
}

#############################################################################

proc ::logger::read_hist_from_file {logfile month} {
    variable options

    lassign [split $month -] year month1
    set filename [file join $options(logdir) $year $month1 $logfile]

    set hist {}
    if {[file exists $filename]} {
	set fd [cdopen $filename r]
	fconfigure $fd -encoding utf-8
	while {[gets $fd line] > 0} {
	    lappend hist [log_to_str $line]
	}
	close $fd
    }

    return $hist
}

#############################################################################

proc ::logger::get_last_messages {jid max interval} {
    if {$max == 0 || $interval == 0} {
	return {}
    }

    set logfile [jid_to_filename $jid]
    set months [lsort -decreasing [get_subdirs $logfile]]
    set messages {}
    set curseconds [clock seconds]
    set max1 [expr {$max - 1}]
    foreach m $months {
	catch {
	    set messages [lsort -increasing -index 1 \
				[concat [read_hist_from_file $logfile $m] \
					$messages]]
	}
	if {$interval > 0} {
	    set idx 0
	    foreach msg $messages {
		set timestamp [lindex $msg 1]
		set seconds [clock scan $timestamp -gmt 1]
		if {$seconds + $interval * 3600 < $curseconds} {
		    incr idx
		} else {
		    break
		}
	    }
	    if {$idx > 0} {
		set messages  [lrange $messages $idx end]
		if {$max > 0 && [llength $messages] >= $max} {
		    return [lrange $messages end-$max1 end]
		} else {
		    return $messages
		}
	    }
	}
	if {$max > 0 && [llength $messages] >= $max} {
	    return [lrange $messages end-$max1 end]
	}
    }
    return $messages
}

#############################################################################

proc ::logger::export {lw mcombo logfile mynick} {
    variable m2d

    set month [$mcombo cget -text]
    if {$month == [::msgcat::mc "All"]} {
	set hist {}
	foreach m [lsort -increasing [get_subdirs $logfile]] {
	    set hist [concat $hist [read_hist_from_file $logfile $m]]
	}
    } else {
	set my_list [split $month " "]
	set month [lindex $my_list end]-$m2d([join [lrange $my_list 0 end-1] " "])
	set hist [read_hist_from_file $logfile $month]
    }

    set filename [tk_getSaveFile -defaultextension .html]
    if {$filename == ""} return
    set fd [open $filename w]
    fconfigure $fd -encoding utf-8

    puts $fd {<?xml version="1.0" encoding="UTF-8"?>}
    puts $fd {<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">}
    puts $fd {<html xmlns="http://www.w3.org/1999/xhtml">}
    set head [jlib::wrapper:createtag head \
		  -subtags [list [jlib::wrapper:createtag link \
				      -vars {
					  rel stylesheet
					  type text/css
					  href tkabber-logs.css
				      }]]]
    puts $fd [jlib::wrapper:createxml $head]


    foreach vars $hist {
	array unset tmp
	if {[catch {array set tmp $vars}]} continue

	set subtags {}
	if {[info exists tmp(timestamp)]} {
	    set seconds [clock scan $tmp(timestamp) -gmt 1]
	    set timestamp [clock format $seconds -format {[%Y-%m-%d %X]}]
	    lappend subtags [jlib::wrapper:createtag span \
				 -vars {class timestamp} \
				 -chdata $timestamp]
	}
	if {[info exists tmp(nick)] && $tmp(nick) != ""} {
	    if {$tmp(nick) == $mynick} {
	        set tag me
	    } else {
	        set tag they
	    }
	    if {[info exists tmp(body)] && [regsub {^/me } $tmp(body) {} body]} {
		set nick "*$tmp(nick) $body"
		unset tmp(body)
	    } else {
		set nick "<$tmp(nick)> "
	    }
	    lappend subtags [jlib::wrapper:createtag span \
				 -vars [list class $tag] \
				 -chdata $nick]

	    if {[info exists tmp(body)]} {
		lappend subtags [jlib::wrapper:createtag span \
				     -vars [list class body] \
				     -chdata "$tmp(body)"]
	    }
	} else {
	    if {[info exists tmp(body)]} {
		lappend subtags [jlib::wrapper:createtag span \
				     -vars [list class server] \
				     -chdata "--- $tmp(body)"]
	    }
	}
	#if {![$l compare "end -1 chars linestart" == "end -1 chars"]} {
	#    puts "\n"
	#}
	set msg [jlib::wrapper:createtag div \
		     -vars [list class message] \
		     -subtags $subtags]

	puts $fd [jlib::wrapper:createxml $msg]
    }
    
    puts $fd {</html>}
    close $fd

    write_css $lw [file join [file dirname $filename] tkabber-logs.css]
}

#############################################################################

proc ::logger::write_css {lw filename} {
    set fd [open $filename w]

    puts $fd "
html body {
    background-color: white;
    color: black;
}

.me {
    color: blue;
}

.they {
    color: red;
}

.server {
    color: green;
}
"

    close $fd
}

#############################################################################
# vim:ts=8:sw=4:sts=4:noet
